Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling central text overflow and adding tooltip for donut charts #26192

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

srmukher
Copy link
Contributor

@srmukher srmukher commented Jan 6, 2023

Handling central text overflow and adding tooltip for donut charts.
Work item: 5321

Previous Behavior

Previous Behavior Example 1:
image

Previous Behavior Example 2:
image

New Behavior

New Behavior Example 1:
image

New Behavior Example 2:
image

Corner cases handled:

Case 1: Text is small : eg: "39,000" => No wrapping or truncation required
image

Case 2: Text is small but words are long (Single line). eg: "Incomprehensibilities" => Truncate horizontally, no wrapping required
image

Case 3: Text is small but words are long (Multi line) : "Tasks are incomprehensible" => Wrap and then truncate horizontally
image

Case 4: Text is long but words are small (Single line). eg: "It is done" => No wrapping or truncation required
image

Case 5: Text is long but words are small (Multi line) : eg: "Handling central text overflow and adding tooltip for donut charts" => After wrapping, if the text exceeds either horizontally (exceeds the central width) or vertically (if it goes beyond the inner circle vertically), then add the ellipsis (truncating) and break.
image

Case 6: Text and words both are long. eg: "Incomprehensibilities happen for texts that are not truncated" => After wrapping, if the text exceeds either horizontally (exceeds the central width) or vertically (if it goes beyond the inner circle vertically), then add the ellipsis (truncating) and break.
image

Case 7: Other languages (Japanese) single line: "やるべきことが残っている" => Truncate horizontally
image

Case 8: Other languages (Japanese) multi line: eg: "53 やるべきことが残っている" => Wrap and then Truncate horizontally
image

Related Issue(s)

https://uifabric.visualstudio.com/iss/_workitems/edit/5321/

  • Fixes #
  • Added text truncation for the central text of Donut chart when it overflows the inner radius of the Donut chart.
  • Added tooltip to show the entire text on hover, when the text inside the Donut chart is truncated.

@fabricteam
Copy link
Collaborator

fabricteam commented Jan 6, 2023

📊 Bundle size report

🤖 This report was generated against d445789f33a50255a6a51548d7282a482bc78a0b

@size-auditor
Copy link

size-auditor bot commented Jan 6, 2023

Asset size changes

Size Auditor did not detect a change in bundle size for any component!

Baseline commit: d445789f33a50255a6a51548d7282a482bc78a0b (build)

@srmukher
Copy link
Contributor Author

srmukher commented Jan 6, 2023 via email

@srmukher
Copy link
Contributor Author

srmukher commented Jan 6, 2023 via email

@codesandbox-ci
Copy link

codesandbox-ci bot commented Jan 6, 2023

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 8ce67c1:

Sandbox Source
@fluentui/react 8 starter Configuration
@fluentui/react-components 9 starter Configuration

@@ -80,6 +111,59 @@ export class Arc extends React.Component<IArcProps, IArcState> {
wrapTextInsideDonut(classNames.insideDonutString, this.props.innerRadius! * 2 - TEXT_PADDING);
}

private _getTruncatedText(text: string, maxWidth: number): string {
Copy link
Contributor

@AtishayMsft AtishayMsft Jan 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work along with wrapTextInsideDonut, if wrapping and truncation both are enabled on the same text

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrapTextInsideDonut ensures that the words are placed in a new line if the current line length exceeds the maximum allowable width inside the donut. However, if a single word itself (even when placed in a new line) exceeds the maxWidth, it does not truncate the word. Thus, _getTruncatedText() determines if truncation is required for a single word or a group of words when inner text exceeds the maximum allowable width.

textAnchor={'middle'}
className={this._classNames.insideDonutString}
y={5}
id={'Donut_center_text'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont hardcode ids

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the wrapping utility has dependency on this Id, need to keep this Id hardcoded


while ((word = words.pop()!)) {
line.push(word);
tspan.text(line.join(' ') + ' ');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are truncating here without wrapping. First try to solve via wrapping and then by truncation. And lets discuss what all possible scenarios are there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the change to wrap first and then truncate. Added 7 corner cases too that were handled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case 5 and case 6, you mentioned you are truncating vertically and horizontally, but you are truncating it only once. Can you clarify on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Atishay, this change is now outdated and I have moved it to the utilities.ts in wrapTextInsideDonut(). After wrapping, if the text exceeds either horizontally (exceeds the central width) or vertically (if it goes beyond the inner circle vertically), then we are adding the ellipsis and breaking. If we want to continue truncation even after the text is truncated one way, we will need to add ellipsis both horizontally and vertically might not be a good experience.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comment is on the description that you have provided for case 5 and case 6. Here you are truncating only once and not both ways. Can you update the description.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated description.

@srmukher srmukher force-pushed the users/srmukher/DonutBugFix branch from c35dd98 to 42d2636 Compare February 2, 2023 07:07
position: 'absolute',
textAlign: 'center',
top: '0px',
background: theme.semanticColors.bodyBackground,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a default theme always available

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the theme is coming as a prop from the parent component, which is a required property

}
// Determine if truncation is required vertically
// If truncation is not required vertically, append a new line while taking care of horizontal truncation
if (tspan.node()!.getBoundingClientRect().y < maxWidth) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldnt this be maxHeight

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since maxHeight is the same as the maxWidth, I have reused maxWidth

line = [word];
tspanEllipsis.text(word);

// Determine if truncation is appending the text exceeds maximum width vertically or horizontally
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by verticl width

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the vertical height, changing the sentence

@srmukher srmukher force-pushed the users/srmukher/DonutBugFix branch from 03d8453 to 66c1fa7 Compare February 23, 2023 10:28
@Hotell
Copy link
Contributor

Hotell commented Apr 17, 2023

CI issue discussed offline with @srmukher , this will unblock your pipeline #27584

@Hotell Hotell removed the request for review from a team May 29, 2023 11:10
this._tooltip = document.getElementById(this._tooltipId);
if (this._tooltip) {
this._tooltip!.innerHTML = text.toString();
this._tooltip!.style.display = 'block';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be done using react states and not direct dom interactions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using react states require the title of the rect to be set in which case, there is no way to override the style of the tooltip as it appears as follows:
tooltip_example
As a result, I have created a ref to the tooltip element as used it instead of querying the dom.

// Determine if truncation is required vertically
// If yes, remove the last line, append ellipsis
// to the last word of the previous line (truncate horizontally) and break
if (tspan.node()!.getBoundingClientRect().y > maxWidth && line.length >= 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be max height?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since maxHeight is the same as the maxWidth, I have reused maxWidth

// Truncate till the text fits in the maximum allowable width
while (tspan.node()!.getComputedTextLength() > maxWidth - ellipsisLength) {
lastWord = line.pop()!;
if (lastWord !== undefined && lastWord.length >= truncateCount) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't break happen only if last word is undefined. Is second part of the expression also relevant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this is relevant because if the (tspan.node()!.getComputedTextLength() > maxWidth - ellipsisLength) and the lastword.length is < truncateCount, popping just the last word is sufficient after which it will append the ellipsis

@@ -118,11 +123,23 @@ export class DonutChartBase extends React.Component<IDonutChartProps, IDonutChar
>
<FocusZone direction={FocusZoneDirection.horizontal} handleTabKey={FocusZoneTabbableElements.all}>
<div>
<div
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use SvgTooltipText control here.

@@ -80,6 +80,7 @@ export class Pie extends React.Component<IPieProps, {}> {
{this.props.valueInsideDonut && (
<text
y={5}
id="Donut_center_text"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use getId?

@@ -25,6 +26,10 @@ export class Arc extends React.Component<IArcProps, IArcState> {
return null;
}

public constructor(props: IArcProps) {
super(props);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change needed?

@@ -1351,11 +1351,12 @@ export function rotateXAxisLabels(rotateLabelProps: IRotateLabelProps) {
return Math.floor(maxHeight / 1.414); // Compute maxHeight/tanInverse(45) to get the vertical height of labels.
}

export function wrapTextInsideDonut(selectorClass: string, maxWidth: number) {
export function wrapTextInsideDonut(selectorClass: string, maxWidth: number, val: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this val is needed as there is only 1 text element inside donut

.text(word);
// append a line only if the line is within the max height
// else truncate horizontally and break
if (tspan.node()!.getBoundingClientRect().y + lineLength < maxWidth) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are you handling the case when text inside donut is "Is in is in in is in"

if (tspan.node()!.getComputedTextLength() > maxWidth && line.length > 1) {
// Determine if wrapping is required
// If yes, append a new line only if it does not exceed the max height
// here, max width = max height, so maxWidth is reused to check max height
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does max height account for the curved shape. The available space for top line/bottom line is less than the middle line.
Or is this scenario too complex to handle.

@AtishayMsft
Copy link
Contributor

@srmukher Reviving this PR. Could you merge master and update this PR for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants